package br.com.wai2008.socket.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import br.com.wai2008.socket.constants.SocketServerConstants;
import br.com.wai2008.socket.service.WAIService;

/**
 * This class implements the execution for a request to the server through
 * socket. It starts a listening to a specific port and executes the 
 * process of send and receive messages using a specific protocol. When a 
 * connection to the socket is accepted, it stops the listening to enable
 * another thread start the listening.<br>
 * It also calls the service execution of the process.
 * 
 * @since 
 * 		September, 2008.
 * 
 * @version
 * 		WAI2008 - version 1.0
 * 
 * @author 
 * 		Andre Romero Goncales
 */
public class SocketServerThread extends Thread {

	/**
	 * Socket Server.
	 */
	private ServerSocket server = null;

	/**
	 * Connection to the socket.
	 */
	private Socket connection = null;

	/**
	 * Send buffer.
	 */
	private BufferedWriter send;	

	/**
	 * Receive buffer.
	 */
	private BufferedReader receive;
	
	/**
	 * Server Socket state.
	 * 
	 * @see
	 * 		br.com.wai2008.socket.server.SocketServerThreadState
	 */
	private int socketServerState = SocketServerThreadState.STATE_INACTIVE;

	/**
	 * Stop the listening to a specific port on server
	 * 
	 * @throws
	 * 		IOException if an error occur while stopping the listener.
	 */
	public void stopListener() throws IOException {
		System.out.println("[SocketServerThread][" + this.getId() + "] Stopping Listener...");
		this.server.close();
		System.out.println("[SocketServerThread][" + this.getId() + "] Listener Stopped");
	}

	/**
	 * Run state from thread. This method start the server, start and 
	 * stop the listening to the port, send and receive messages to/from 
	 * the client and calls the service execution.
	 */
	public void run() {
		try {
			System.out.println("[SocketServerThread][" + this.getId() + "] Running Thread");
			this.socketServerState = SocketServerThreadState.STATE_READY;
			this.startServer();
  	    	this.stopListener();
  	    	SocketServer.startNewThread();
  			this.socketServerState = SocketServerThreadState.STATE_WORKING;
  	    	this.getBuffer();
  	    	String inputMessage = this.receiveMessage();
  	    	String outputMessage = this.executeService(inputMessage);
  	    	this.sendMessage(outputMessage);
		} catch (SocketException e) { 
			System.out.println("[SocketServerThread][" + this.getId() + "] (" + SocketServerConstants.SERVER_HOST + ":" + 
					SocketServerConstants.SERVER_PORT + ") Listener closed");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
  			this.socketServerState = SocketServerThreadState.STATE_FINISHED;
  			SocketServer.removeThread(this);
		}
	}

	/**
	 * Start the server and wait a request for connection to it.
	 * You may set the server port and server timeout to the connection.
	 * 
	 * @see 
	 * 		br.com.wai2008.socket.constants.SocketServerConstants
	 * 
	 * @throws
	 * 		IOException if an error occur while start the server or wait
	 * 		for a connection.
	 */
	private void startServer() throws IOException {	
		System.out.println("[SocketServerThread][" + this.getId() + "] Starting Server...");
		this.server = new ServerSocket(SocketServerConstants.SERVER_PORT);
		this.server.setSoTimeout(SocketServerConstants.SERVER_TIMEOUT);
		System.out.println("[SocketServerThread][" + this.getId() + "] Server Started on " + SocketServerConstants.SERVER_HOST + 
				":" + SocketServerConstants.SERVER_PORT);
  	    while(true) {
  	    	try {
  			     this.waitConnection();  	    		
  	    	} catch (SocketTimeoutException ste) {
  	  		System.out.println("[SocketServerThread][" + this.getId() + "] Waiting Timed Out after " + SocketServerConstants.SERVER_TIMEOUT);
  	    		continue;
  	    	}
  	    	break;
	    }
	}

	/**
	 * Wait a connection from the client to the server.
	 * 
	 * @throws
	 * 		IOException if an error occur while wait for a connection.
	 * @throws
	 * 		SocketTimeoutException if a timeout occur while wait for a 
	 * 		connection.
	 */
	private void waitConnection() throws IOException {	
		System.out.println("[SocketServerThread][" + this.getId() + "] Waiting Connection...");
	    this.connection = this.server.accept();
		System.out.println("[SocketServerThread][" + this.getId() + "] Connection Done: IP[" + this.connection.getInetAddress().getHostAddress() + 
				"] NAME[" + this.connection.getInetAddress().getHostName() + "]");
	}

	/**
	 * Get a send and receive buffers.
	 * 
	 * @throws
	 * 		IOException if the stream of output or input cannot be gotten.
	 */
	private void getBuffer() throws IOException {
		this.send = new BufferedWriter(new OutputStreamWriter(this.connection.getOutputStream()));
		this.send.flush();
		this.receive = new BufferedReader(new InputStreamReader(this.connection.getInputStream()));
	}

	/**
	 * Send a message to the client in a specific protocol.
	 * 
	 * @see S
	 * 		br.com.wai2008.socket.protocol.ClientToServerProtocol
	 * 
	 * @param
	 * 		message message to be sent.
	 * 
	 * @throws
	 * 		IOException if the message cannot be sent.
	 */
	private void sendMessage(String message) throws IOException {
		System.out.println("[SocketServerThread][" + this.getId() + "] Sending Message: " + message);
	   	this.send.write(message);
	   	this.send.flush();
		System.out.println("[SocketServerThread][" + this.getId() + "] Message sent");
	}

	/**
	 * Receive a message to the client in a specific protocol.
	 * 
	 * @see 
	 * 		br.com.wai2008.socket.protocol.ClientToServerProtocol
	 * 
	 * @return
	 * 		Message received.
	 * 
	 * @throws
	 * 		IOException if the message cannot be read.
	 */
	private String receiveMessage() throws IOException {
		System.out.println("[SocketServerThread][" + this.getId() + "] Receiving Message...");
		String message = this.receive.readLine();
		System.out.println("[SocketServerThread][" + this.getId() + "] Message Received: " + message);
		return message;
	}

	/**
	 * Get the Socket Server's state.
	 * 
	 * @return
	 * 		0 - if state is INATIVE.<br>
	 * 		1 - if state is READY.<br>
	 * 		2 - if state is WORKING.<br>
	 * 		3 - if state is FINISHED.
	 * 
	 * @see
	 * 		br.com.wai2008.socket.server.SocketServerThreadState
	 */
	public int getSocketServerState() {
		return socketServerState;
	}

	/**
	 * Executes the WAI service.
	 * 
	 * @see 
	 * 		br.com.wai2008.socket.protocol.ClientToServerProtocol 
	 * @see
	 * 		br.com.wai2008.socket.server.SocketServerThreadState
	 * 
	 * @param
	 * 		inputMessage message received from the client. This message
	 * 		must be in a ClientToServerProtocol protocol.
	 * 
	 * @return
	 * 		Message to be sent to the client. This message
	 * 		must be in a ClientToServerProtocol protocol.
	 */
	private String executeService(String inputMessage) {
		WAIService waiService = new WAIService();
	    String outputMessage = waiService.execute(inputMessage);
		return outputMessage;
	}
}
